iT邦幫忙

2024 iThome 鐵人賽

DAY 4
0

好我覺得前面真的是在水,你們可以看看 Remix Tutorial,如果熟悉 React 跟 SSR 應該 30 分鐘真的可以完成,我的話當時因為真的不太熟,是看了四個小時才看完。

Loader function

Ref: Remix Loader

這個是 Remix 伺服器端兩大頭 loader、action 之一,在 build 時會自動分類到 Server 端,所以這裡面執行的所有 function 在前端都是無法調用的,主要是處理 cookie、session 與資料取得,然後在前端的程式碼以 useLoaderData() 取用,下面來展示一下。

新的 React router 其實就是 remix 製作的東東是一樣的,這部分會寫在 route 資料夾裡頭,也可以自己整理在 *.server.ts。Loader 會在 GET 時執行,然後 return 伺服器的 response。

伺服器端取得資料

資料流程是:

Auth

  1. 送 request 到自訂的 getUserSession
  2. 把 cookie 送給 Firebase admin SDK 取得 decodedIdToken
  3. return 從 Token 中取得的 uid 與 customClaim 或是 null

Get User

  1. 沒 User 的話 redirect(也可以用 throw redirect,Remix 會接住)
  2. 有的話從 Firebase(或是你的 DB)取得完整資料
  3. Return { data: { ...user } }

我有發現如果在 return json({ }) 裡面包含 toJSON() 的話會造成 useLoaderData<typeof loader>() 無法正確取得 type(user 會是 unknown)要避免,所以我 return 時解構 { data: { ...user } },這樣會自動篩掉 toJSON()。

*記得如果是寫在 loader 外面的資料,都會被曝在前端哦!

// file: /app/router/profile.tsx
// 我寫的 getUserSession 會從 request header 取得 cookie 之後解析 session,然後向 Firebase 取得 decodedIdToken。

import type { LoaderFunctionArgs } from '@remix-run/node'
import { json, redirect, useLoaderData } from '@remix-run/react'
import { getUserSession } from '~/data/session.server'
import { firebase } from '~/data/_firebase.server'

// loader 除了 request 之外也可以取得 route params 請見上篇
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
    const userSession = await getUserSession(request)
    
    if (!userSession) {
        return redirect('/signin')
    }
    if (!userSession.decodedIdToken.email_verified) {
        return redirect('/verify')
    }
    
    const user = await firebase.auth().getUser(userSession.decodedIdToken.uid)

    // 用 remix 提供的 json 返回資料,可以在 json 後面用 <> 自定義你的 type
    return json({
        data: { ...user },
    })

前端取得資料

在同一個文件中,以 export default function() 寫 React。

// file: /app/router/profile.tsx

export default function Account() {
    // 用 typeof loader remix 會自己 refer type
    const { data: user } = useLoaderData<typeof loader>()
    
    return (
        // reder 你的 user
    )
}

就這樣你就輕鬆做了一個前後端的 app 了!

Super Tip

因為 server 會把資料轉換成 JSON 送給前端,所以前端 type 如下,是 JsonifyObject:

https://ithelp.ithome.com.tw/upload/images/20240831/20162937mpx5eYSl6H.png

這會是個大問題,如果把他直接傳給要求 Date 的 component,例如 createdAt: Date 就會出現:

https://ithelp.ithome.com.tw/upload/images/20240831/20162937Bj6kvBMPsp.png

這時候你就會想,啊 type 就是寫 createdAt: Date 為什麼不行?問題就是出在 JsonifyObject 會把 Date 轉換成 string,所以其實在前端取得的 JsonifyObject<createdAt: Date> 其實是 createdAt: string

要取得這個轉換後的 type,可以使用type SerializedLoaderDate = SerializeFrom<typeof loader>

這樣 SerializedLoaderData 就是你 useLoaderData 給前端的 type,也不需要自己定義 component 要接收什麼,一切 type 都依據 server 給的資料定義。


上一篇
[D3] Remix 架構:檔案們、app/*、Route File Naming
下一篇
[D5] Remix Action
系列文
30 天練成全端 Remix 大師6
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言